﻿using Codemonk.Pease.Common;
using System;
using System.Reflection;

namespace Codemonk.Pease.Server
{
    /// <summary>
    /// 基于约定的方法执行器
    /// 这个执行器会将命名空间中的Interfaces替换为Services,并将接口后面添加Implementation
    /// </summary>
    /// <typeparam name="T">能够解析的数据类型</typeparam>
    public class MethodInvoker<C, T> : IMethodInvoker<C, T>
    {
        /// <summary>
        /// 内部执行器
        /// </summary>
        /// <typeparam name="D">能够承载的数据类型</typeparam>
        private sealed class InnerInvoker<D, CT>
        {
            public event EventHandler OnCompleted;

            private readonly ISerializater<D> serializater;
            private readonly ResponseAgreement<D> response;
            private readonly RequestAgreement<D> request;
            private readonly IInterfaceActivator activator;
            private readonly IObjectDecorator<CT> decorator;
            private readonly IAspectMonitor<CT> monitor;
            private readonly CT context;
            private bool hasCompleted;
            private object instance;
            private object entity;
            private Type backEntityType;
            private string methodName;
            public ResponseAgreement<D> Response
            {
                get { return response; }
            }

            public InnerInvoker(RequestAgreement<D> request, IInterfaceActivator activator, ISerializater<D> serializater, IObjectDecorator<CT> decorator, CT context, IAspectMonitor<CT> monitor)
            {
                if (activator == null) throw new Exception("激活器为空");
                if (serializater == null) throw new Exception("序列化器为空");
                this.request = request;
                this.activator = activator;
                this.serializater = serializater;
                this.decorator = decorator;
                this.context = context;
                this.monitor = monitor;
                this.response = new ResponseAgreement<D>();
            }

            public void Invoke()
            {
                try
                {
                    Type targetType = null;
                    instance = activator.CreateInstance(request.InterfaceName, out targetType);
                    var method = FindMethod(targetType, request.MethodSignature);
                    methodName = method.Name;
                    var parameters = method.GetParameters();
                    var entityType = parameters[0].ParameterType;
                    backEntityType = parameters[2].ParameterType.GetGenericArguments()[0];
                    Action<Exception> error = SetException;
                    Delegate deserializer = Delegate.CreateDelegate(typeof(Func<,>).MakeGenericType(typeof(string), entityType), this, DeserializeInfo.MakeGenericMethod(entityType));
                    entity = deserializer.DynamicInvoke(request.Data);
                    Delegate back = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(backEntityType), this, SetDataInfo.MakeGenericMethod(backEntityType));
                    if (decorator != null) decorator.Decorate(instance, context);
                    if (monitor != null)
                    {
                        try
                        {
                            monitor.BeforeInvoke(methodName, context, instance, entity, backEntityType, obj => MonitorSetData(obj));
                            if (!hasCompleted) CatchInvoke(method, error, back);
                        }
                        catch (NotImplementedException)
                        {
                            CatchInvoke(method, error, back);
                        }
                    }
                    else
                    {
                        CatchInvoke(method, error, back);
                    }
                }
                catch (Exception e)
                {
                    SetException(e);
                }
            }
            private void CatchInvoke(MethodInfo method, Action<Exception> error, Delegate back)
            {
                try
                {
                    method.Invoke(instance, new object[] { entity, error, back });
                }
                catch (System.Exception ex)
                {
                    if (monitor != null)
                    {
                        try
                        {
                            monitor.CatchException(methodName, context, instance, entity, backEntityType, ex, obj => MonitorSetData(obj));
                            if (!hasCompleted) SetException(ex);
                        }
                        catch (NotImplementedException)
                        {
                            SetException(ex);
                        }
                    }
                    else
                    {
                        SetException(ex);
                    }
                }
            }

            private static MethodInfo FindMethod(Type type, string methodSignature)
            {
                var methods = type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);
                MethodInfo mi = null;
                foreach (var method in methods)
                {
                    var nm = method.ToString();
                    if (nm == methodSignature)
                    {
                        mi = method;
                        break;
                    }
                }
                return mi;
            }

            private DT Deserialize<DT>(D data)
            {
                return serializater.Deserialize<DT>(data);
            }
            private static MethodInfo deserializeInfo;
            private static MethodInfo DeserializeInfo
            {
                get
                {
                    if (deserializeInfo == null)
                    {
                        var t = typeof(InnerInvoker<D, CT>);
                        deserializeInfo = t.GetMethod("Deserialize", BindingFlags.NonPublic | BindingFlags.Instance);
                    }
                    return deserializeInfo;
                }
            }

            private void SetException(Exception e)
            {
                try
                {
                    response.ErrorMessage = e.Message;
                }
                finally
                {
                    hasCompleted = true;
                    if (OnCompleted != null) OnCompleted(this, EventArgs.Empty);
                }
            }
            private void MonitorSetData(object i)
            {
                if (i == null)
                {
                    SetData(i);
                }
                else
                {
                    var type = i.GetType();
                    if (type == backEntityType) SetData(i);
                }
            }
            private void SetData<DT>(DT i)
            {
                try
                {
                    if (monitor != null)
                    {
                        try
                        {
                            monitor.AfterInvoke(methodName, context, instance, entity, i, backEntityType, obj =>
                            {
                                try
                                {
                                    response.Data = serializater.Serialize(obj);
                                    hasCompleted = true;
                                }
                                catch { }
                            });
                            if (!hasCompleted) response.Data = serializater.Serialize(i);
                        }
                        catch (NotImplementedException)
                        {
                            response.Data = serializater.Serialize(i);
                        }
                    }
                    else
                    {
                        response.Data = serializater.Serialize(i);
                    }
                }
                catch (System.Exception e)
                {
                    response.ErrorMessage = e.Message;
                }
                finally
                {
                    hasCompleted = true;
                    if (OnCompleted != null) OnCompleted(this, EventArgs.Empty);
                }
            }
            private static MethodInfo setDataInfo;
            private static MethodInfo SetDataInfo
            {
                get
                {
                    if (setDataInfo == null)
                    {
                        var t = typeof(InnerInvoker<D, CT>);
                        setDataInfo = t.GetMethod("SetData", BindingFlags.NonPublic | BindingFlags.Instance);
                    }
                    return setDataInfo;
                }
            }
        }
        public virtual void Invoke(
            C context,
            IAspectMonitor<C> monitor,
            IObjectDecorator<C> decorator,
            IInterfaceActivator activator,
            ISerializater<T> serializater,
            RequestAgreement<T> request,
            Action<ResponseAgreement<T>> back)
        {
            if (back == null) return;
            var invoker = new InnerInvoker<T, C>(request, activator, serializater, decorator, context, monitor);
            EventHandler handler = null;
            handler = (m, n) =>
            {
                var ivk = m as InnerInvoker<T, C>;
                if (ivk == null) return;
                ivk.OnCompleted -= handler;
                back(ivk.Response);
            };
            try
            {
                invoker.OnCompleted += handler;
                invoker.Invoke();
            }
            catch (System.Exception ex)
            {
                invoker.OnCompleted -= handler;
                ResponseAgreement<T> response = new ResponseAgreement<T>();
                response.ErrorMessage = ex.Message;
                back(response);
            }
        }
    }
}
